import Fluent
import FluentSQLiteDriver
import Leaf
import Logging
import NIOCore
import NIOPosix
import Vapor

// stage2 ref link:
// https://docs.vapor.codes/zh/fluent/overview/#identifier
// https://blog.vapor.codes/posts/fluent-models-and-sendable/

@main
enum Entrypoint {
    static func main() async throws {
        var env: Environment = try Environment.detect()
        try LoggingSystem.bootstrap(from: &env)

        let app: Application = try await Application.make(env)

        do {
            try await configure(app)
        } catch {
            app.logger.report(error: error)
            try? await app.asyncShutdown()
            throw error
        }
        try await app.execute()
        try await app.asyncShutdown()
    }
}

public func configure(_ app: Application) async throws {
    app.logger.logLevel = .debug
    try routes(app)
    app.databases.use(.sqlite(.file("db.sqlite")), as: .sqlite)
    app.migrations.add(CreateGalaxy())
    app.migrations.add(CreateStar())
    app.views.use(.leaf)

    let corsConfiguration = CORSMiddleware.Configuration(
        allowedOrigin: .all,
        allowedMethods: [.GET, .POST, .PUT, .OPTIONS, .DELETE, .PATCH],
        allowedHeaders: [
            .accept, .authorization, .contentType, .origin, .xRequestedWith, .userAgent,
            .accessControlAllowOrigin,
        ]
    )
    let cors = CORSMiddleware(configuration: corsConfiguration)
    let error = ErrorMiddleware.default(environment: app.environment)
    // 清除现有的 middleware。
    app.middleware = .init()
    app.middleware.use(cors)
    app.middleware.use(error)
}

func routes(_ app: Application) throws {

    app.get("galaxies") { req async throws in
        try await Galaxy.query(on: req.db).all()
    }

    app.post("galaxies") { req async throws -> Galaxy in
        let galaxy: Galaxy = try req.content.decode(Galaxy.self)
        try await galaxy.create(on: req.db)
        return galaxy
    }

    app.post("stars") { req async throws -> Star in
        let star: Star = try req.content.decode(Star.self)
        try await star.create(on: req.db)
        return star
    }

    app.get("galaxies") { req in
        try await Galaxy.query(on: req.db).with(\.$stars).all()
    }

    app.get("hello") { req async throws -> View in
        return try await req.view.render("hello", ["name": "swift vapor with leaf"])
    }

    app.get("home") { req async throws -> View in
        struct WelcomeContext: Encodable {
            var title: String
            var numbers: [Int]
        }
        return try await req.view.render(
            "home", WelcomeContext(title: "This is title defined at backend!", numbers: [42, 9001]))
    }
}

final class Galaxy: Model, Content, @unchecked Sendable {
    // 表或集合名。
    static let schema: String = "galaxies"

    // 星系唯一标识符。
    @ID(key: .id)
    var id: UUID?

    // 星系名称。
    @Field(key: "name")
    var name: String

    // 这个星系的所有恒星。
    @Children(for: \.$galaxy)
    var stars: [Star]

    // 创建一个空的星系。
    init() {}

    // 创建一个新的星系并设置所有属性。
    init(id: UUID? = nil, name: String) {
        self.id = id
        self.name = name
    }
}

final class Star: Model, Content, @unchecked Sendable {
    // 表或集合名。
    static let schema = "stars"

    // Star 唯一标识符。
    @ID(key: .id)
    var id: UUID?

    // Star 名称
    @Field(key: "name")
    var name: String

    // 引用这颗恒星所在的星系。
    @Parent(key: "galaxy_id")
    var galaxy: Galaxy

    // 创建一个空的 Star。
    init() {}

    // 创建一个新的 Star，设置所有属性。
    init(id: UUID? = nil, name: String, galaxyID: UUID) {
        self.id = id
        self.name = name
        self.$galaxy.id = galaxyID
    }
}

struct CreateGalaxy: AsyncMigration {
    // 为存储 Galaxy 模型准备数据库。
    func prepare(on database: Database) async throws {
        try await database.schema("galaxies")
            .id()
            .field("name", .string)
            .create()
    }

    // 可选地恢复 prepare 方法中所做的更改。
    func revert(on database: Database) async throws {
        try await database.schema("galaxies").delete()
    }
}

struct CreateStar: AsyncMigration {
    // 为存储 Star 模型准备数据库。
    func prepare(on database: Database) async throws {
        try await database.schema("stars")
            .id()
            .field("name", .string)
            .field("galaxy_id", .uuid, .references("galaxies", "id"))
            .create()
    }

    // 可选地恢复 prepare 方法中所做的更改。
    func revert(on database: Database) async throws {
        try await database.schema("stars").delete()
    }
}
